﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Text.RegularExpressions;
using SlimDX.D3DCompiler;

namespace HlslCompiler
{
    public class CompilerSettingsParser
    {

        private const string CompilerSettingsRegex = @"<effectEd(?<Content>.*?)/>";
        private const string AttributeRegex = @"(?<Attribute>\w+)=""(?<Value>[a-zA-Z0-9_, ]+)""";

        public static string SetCompilerSettings(string source, CompilerSettings compilerSettings)
        {
            var compilerSettingsString = FormatCompilerSettings(compilerSettings);
            var regex = new Regex(CompilerSettingsRegex, RegexOptions.IgnoreCase);
            var match = regex.Match(source);
            if (match.Success)
            {
                return source.Remove(match.Index, match.Length).Insert(match.Index, compilerSettingsString);
            }
            return source.Insert(0, String.Format("// {0}\r\n", FormatCompilerSettings(compilerSettings)));
        }

        public static bool HasCompilerSettings(string source)
        {
            return (new Regex(CompilerSettingsRegex, RegexOptions.IgnoreCase)).IsMatch(source);
        }

        public static CompilerSettings GetCompilerSettings(string source)
        {
            var regex = new Regex(CompilerSettingsRegex, RegexOptions.IgnoreCase);
            var match = regex.Match(source);
            if (match.Success)
            {
                var matches = new Regex(AttributeRegex, RegexOptions.IgnoreCase).Matches(match.Groups["Content"].Value);
                var settings = new CompilerSettings();
                foreach (Match m in matches)
                {
                    var attributeName = m.Groups["Attribute"].Value;
                    var value = m.Groups["Value"].Value;
                    switch (attributeName.ToLower())
                    {
                        case "direct3d":
                            settings.Direct3D = value;
                            break;
                        case "profile":
                            settings.Profile = value;
                            break;
                        case "entrypoint":
                            settings.EntryPoint = value;
                            break;
                        case "shaderflags":
                            settings.ShaderFlags = ParseShaderFlags(value);
                            break;
                        case "effectflags":
                            settings.EffectFlags = ParseEffectFlags(value);
                            break;
                    }
                }
                return settings;
            }
            return null;
        }

        private static string FormatCompilerSettings(CompilerSettings settings)
        {
            var optionals = new StringBuilder();
            if (!string.IsNullOrWhiteSpace(settings.EntryPoint)) optionals.AppendFormat(@" entryPoint=""{0}""", settings.EntryPoint);
            if (settings.ShaderFlags != ShaderFlags.None) optionals.AppendFormat(@" shaderFlags=""{0}""", GetShaderFlagsString(settings.ShaderFlags));
            if (settings.EffectFlags != EffectFlags.None) optionals.AppendFormat(@" effectFlags=""{0}""", GetEffectFlagsString(settings.EffectFlags));
            if (!string.IsNullOrWhiteSpace(settings.EntryPoint)) optionals.AppendFormat(@" entryPoint=""{0}""", settings.EntryPoint);
            return string.Format(@"<effectEd direct3D=""{0}"" profile=""{1}""{2} />", settings.Direct3D, settings.Profile, optionals);
        }

        private static string GetEffectFlagsString(EffectFlags effectFlags)
        {
            var text = effectFlags.ToString();
            return text;
        }

        private static string GetShaderFlagsString(ShaderFlags shaderFlags)
        {
            var text = shaderFlags.ToString();
            return text;
        }

        private static ShaderFlags ParseShaderFlags(string source)
        {
            var result = ShaderFlags.None;
            var first = true;
            foreach (var flag in source.Split(','))
            {
                if (!string.IsNullOrWhiteSpace(flag))
                {
                    ShaderFlags value;
                    if (Enum.TryParse(flag.Trim(), true, out value))
                    {
                        if (first)
                        {
                            result = value;
                            first = false;
                        }
                        else
                        {
                            result = result | value;
                        }
                    }
                    else
                    {
                        throw new Exception(string.Format("ShaderFlag \"{0}\" is not supported. Correct or remove it first.", flag));
                    }
                }
            }
            return result;
        }

        private static EffectFlags ParseEffectFlags(string source)
        {
            var result = EffectFlags.None;
            var first = true;
            foreach (var flag in source.Split(','))
            {
                if (!string.IsNullOrWhiteSpace(flag))
                {
                    EffectFlags value;
                    if (Enum.TryParse(flag, true, out value))
                    {
                        if (first)
                        {
                            result = value;
                            first = false;
                        }
                        else
                        {
                            result = result | value;
                        }
                    }
                    else
                    {
                        throw new Exception(string.Format("EffectFlag \"{0}\" is not supported. Correct or remove it first.", flag));
                    }
                }
            }
            return result;
        }


    }
}
